#include "http_conn.h"

void setnonblocking(int fd){
    int old_flag=fcntl(fd,F_GETFL);
    old_flag|=O_NONBLOCK;
    fcntl(fd,F_SETFL,old_flag);
}
/*
LT水平触发模式：epoll_wait检测到文件描述符有事件发生，则将其通知给应用程序，应用程序可以不立即处理该事件。
当下一次调用epoll_wait时，epoll_wait还会再次向应用程序报告此事件，直至被处理
ET边缘触发模式：epoll_wait检测到文件描述符有事件发生，则将其通知给应用程序，
应用程序必须立即处理该事件必须要一次性将数据读取完，使用非阻塞I/O，读取到出现eagain
EPOLLONESHOT：一个线程读取某个socket上的数据后开始处理数据，
在处理过程中该socket上又有新数据可读，此时另一个线程被唤醒读取，此时出现两个线程处理同一个socket。
我们期望的是一个socket连接在任一时刻都只被一个线程处理，通过epoll_ctl对该文件描述符注册epolloneshot事件，
一个线程处理socket时，其他线程将无法处理，当该线程处理完后，需要通过epoll_ctl重置epolloneshot事件。
*/
void addfd(int epollfd,int fd,bool one_shot){
    /*
        event是epoll_event结构体指针类型，表示内核所监听的事件，具体定义如下：
        struct epoll_event {
            __uint32_t events; // Epoll events 
            epoll_data_t data; // User data variable 
        };
        events描述事件类型，其中epoll事件类型有以下几种
        EPOLLIN：表示对应的文件描述符可以读（包括对端SOCKET正常关闭）
        EPOLLOUT：表示对应的文件描述符可以写
        EPOLLPRI：表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）
        EPOLLERR：表示对应的文件描述符发生错误
        EPOLLHUP：表示对应的文件描述符被挂断；
        EPOLLET：将EPOLL设为边缘触发(Edge Triggered)模式，这是相对于水平触发(Level Triggered)而言的
        EPOLLONESHOT：只监听一次事件，当监听完这次事件之后，如果还需要继续监听这个socket的话，需要再次把这个socket加入到EPOLL队列里
        EPOLLSHOT的作用主要用于多线程中

epoll在某次循环中唤醒一个事件，并用某个工作进程去处理该fd，此后如果不注册EPOLLSHOT,
在该fd时间如果工作线程处理的不及时，主线程仍会唤醒这个时间，并另派线程池中另一个线程也来处理这个fd。
为了避免这种情况，需要在注册时间时加上EPOLLSHOT标志，EPOLLSHOT相当于说，某次循环中epoll_wait唤醒该事件fd后，
就会从注册中删除该fd,也就是说以后不会epollfd的表格中将不会再有这个fd,也就不会出现多个线程同时处理一个fd的情况。

ET是一次事件只会触发一次,如一次客户端发来消息,fd可读,epoll_wait返回.等下次再调用epoll_wait则不会返回了
LT是一次事件会触发多次,如一次客户端发消息,fd可读,epoll_wait返回,不处理这个fd,再次调用epoll_wait,立刻返回,
    */
    epoll_event event;
    event.data.fd=fd;
    //边缘触发（ET）   条件触发（LT）
    // event.events=EPOLLIN|EPOLLRDHUP;//EPOLLET边沿触发模式？？   水平 ？？ 
    event.events=EPOLLIN|EPOLLET|EPOLLRDHUP;//EPOLLET边沿触发模式？？   水平 ？？ 

     /* 针对connfd，开启EPOLLONESHOT，因为我们希望每个socket在任意时刻都只被一个线程处理 */
    if(one_shot){
        event.events|=EPOLLONESHOT;
    }
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
    //设置文件描述符非阻塞
    setnonblocking(fd);

}

void removefd(int epollfd,int fd){
    epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);
    close(fd);
}

void modfd(int epollfd,int fd,int ev){
    epoll_event event;
    event.data.fd=fd;
    //重置socket上EPOLLONESHOT事件，以确保下一次可读时，EPOLLIN事件能被触发
    event.events=ev|EPOLLONESHOT|EPOLLRDHUP;//?
    epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event); 
}

//由线程池中的工作线程调用，处理http请求的入口函数
void http_conn::process(){//响应、处理客户端请求
    //解析http请求
    printf("parse request, create reponse\n");
    //生成响应
}

//初始化新接受的连接
void http_conn::init(int connfd,const struct sockaddr_in &client_address){
    m_sockfd=connfd;
    m_address=client_address;
    //端口复用
    int reuse=1;
    setsockopt(m_sockfd,SOL_SOCKET,SO_REUSEPORT,&reuse,sizeof(reuse));
    //添加到epoll对象中
    addfd(m_epollfd,m_sockfd,true);//要oneshot
    ++m_user_count;
    init();
}

void http_conn::init(){
    m_read_index=0;
    m_checked_index=0;
    m_start_line=0;
    m_check_state=CHECK_STATE_REQUESTLINE;

    m_url=nullptr;//请求目标文件的文件名
    m_version=nullptr;//协议版本，只支持HTTP1.1
    m_method=GET;//请求方法

    bzero(m_read_buf,READ_BUFFER_SIZE);

    m_host=nullptr;
    m_linger=false;

    m_content_length=0;
}

//关闭连接
void http_conn::close_conn(){
    if(m_sockfd!=-1){
        removefd(m_epollfd,m_sockfd);
        m_sockfd=-1;
        --m_user_count; 
    }
}

//非阻塞的读 循环读取客户数据直到无数据可读或对方关闭连接
bool http_conn::read(){
    // printf("read all\n");
    if(m_read_index>=READ_BUFFER_SIZE){
        return false;
    }

    //读取到的字节
    int bytes_read=0;
    while(1){
        bytes_read=recv(m_sockfd,m_read_buf+m_read_index,READ_BUFFER_SIZE-m_read_index,0);
        if(bytes_read==-1){
            if(errno==EAGAIN||errno==EWOULDBLOCK){
                //没有数据
                break;//(读完了
            }
            return false;
        }else if(bytes_read==0){
            //对方关闭连接
            return false;
        }
        m_read_index+=bytes_read;
        printf("读取到了数据\n%s",m_read_buf);
    }
    return true;
}

//非阻塞的写
bool http_conn::write(){
    printf("write all\n");
    return true;
}

int http_conn::m_epollfd=-1;
int http_conn::m_user_count=0;


//解析HTTP请求
//主状态机
http_conn::HTTP_CODE http_conn::process_read(){
    LINE_STATUS line_status=LINE_OK;
    HTTP_CODE ret=NO_REQUEST;
    char* text=nullptr;
    while(((LINE_STATUS=parse_line())==LINE_OK)||((m_check_state==CHECK_STATE_CONTENT)&&(line_status==LINE_OK))){
        //解析到了一行完整的数据 或 解析到了请求体

        //获取一行数据
        text=get_line();

        m_start_line=m_checked_index;
        printf("got 1 http line: %s\n",text);

        switch(m_check_state){
            case CHECK_STATE_REQUESTLINE:{
                ret=parse_request_line(text);
                if(ret==BAD_REQUEST){
                    return ret;
                }
                break;
            }
            case CHECK_STATE_HEADER:{
                ret=parse_headers(text);
                if(ret==BAD_REQUEST){
                    return ret;
                }else if(ret==GET_REQUEST){
                    return do_request();//解析具体信息
                }
                break;
            }
            case CHECK_STATE_CONTENT:{
                ret=parse_content(text);
                if(ret==GET_REQUEST){
                    return do_request();
                }
                line_status=LINE_OPEN;
                break;
            }
            default:{
                return INTERNAL_ERROR;
            }
        }
        return NO_REQUEST;
    }
}

//解析请求首行，获得请求方法目标url，HTTP版本
http_conn::HTTP_CODE http_conn::parse_request_line(char* text){
    // GET /index.html HTTP/1.1
    m_url=strpbrk(text," \t");

    //GET\0/index.html HTTP/1.1
    *m_url++='\0';

    char* method=text;
    if(strcasecmp(method,"GET")==0){
        m_method=GET;
    }else{
        return BAD_REQUEST;
    }

    // /index.html HTTP/1.1
    m_version=strpbrk(m_url," \t");
    if(m_version==nullptr){
        return BAD_REQUEST;
    }
    // /index.html\0HTTP/1.1
    *m_version++='\0';
    if(strcasecmp(m_version,"HTTP/1.1")!=0){
        return BAD_REQUEST; 
    }

    //http://192.168.1.1:10000/index.html
    if(strcasecmp(m_url,"http://")==0){
        m_url+=7;
        m_url=strchr(m_url,'/');
    }

    if(m_url==nullptr||m_url[0]!='/'){
        return BAD_REQUEST;
    }

    m_check_state=CHECK_STATE_HEADER;

    return NO_REQUEST;
}

//解析请求头
http_conn::HTTP_CODE http_conn::parse_headers(char* text){
    //遇到空行 解析完成
    if(text[0]=='\0'){
        //如果有消息体
        if(m_content_length!=0){
            m_check_state=CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        return GET_REQUEST;
    }else if(strncasecmp(text,"Connection:",11)==0){
        text+=11;
        text+=strspn(text," \t");
        if(strcasecmp(text,"keep-alive")==0){
            m_linger=true;
        }

    }else if(strncasecmp(text,"Content-length:",15)==0){
        text+=15;
        text+=strspn(text," \t");
        m_content_length=atol(text);
    }else if(strncasecmp(text,"Host:",5)==0){
        text+=5;
        text+=strspn(text," \t");
        m_host=text;
    }else{
        printf("oop!unknow header %s\n",text);
    }
    return NO_REQUEST;
}

//解析请求体
http_conn::HTTP_CODE http_conn::parse_content(char* text){
    //目前只判断了是否完整读入
    if(m_read_index>=(m_content_length+m_checked_index)){
        text[m_content_length]='\0';
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

//解析一行，判断依据\r\n
http_conn::LINE_STATUS http_conn::parse_line(){
    char temp;
    for(m_checked_index;m_checked_index<m_read_index;++m_checked_index){
        temp=m_read_buf[m_checked_index];
        if(temp=='\r'){
            if(m_checked_index+1==m_read_index){
                return LINE_OPEN;
            }else if(m_read_buf[m_checked_index+1]=='\n'){
                m_read_buf[m_checked_index]='\0';
                ++m_checked_index;
                m_read_buf[m_checked_index]='\0';
                ++m_checked_index;
                return LINE_OK;
            }
            return LINE_BAD;
        }else if(temp=='\n'){
            if(m_checked_index>0&&m_read_buf[m_checked_index-1]=='\r'){
                m_read_buf[m_checked_index-1]='\0';
                m_read_buf[m_checked_index]='\0';
                ++m_checked_index;
                return LINE_OK;
            }
            return LINE_BAD;
        }
        return LINE_OPEN;
    }
    return LINE_OK;
}

/*
  当得到一个正确的，完整的HTTP请求时，我们就分析目标文件的属性，
  如果目标文件存在、对所有用户可读，且不是目录，则使用
*/
http_conn::HTTP_CODE http_conn::do_request(){
    //   /home/lauirf/test/server/webserver/resourece
    strcpy(my_real_file,doc_root);
    int len=strlen(doc_root);
    strncpy(my_real_file+len,m_url,FILENAME_LEN-len-1);//拼接

    if(stat(my_real_file,&m_file_stat)<0){
        return NO_REQUEST;
    }

    //判断访问权限
    if(!(m_file_stat.st_mode&S_IROTH)){
        return FORBIDDEN_REQUEST;
    }

    //判断是否是目录
    if(S_ISDIR(m_file_stat.st_mode)){
        return BAD_REQUEST;
    }

    //以只读方式打开
    int fd=open(my_real_file,O_RDONLY);
    //创建内存映射
    my_file_address=(char*)mmap(0,m_file_stat.st_size,PROT_READ,MAP_PRIVATE,fd,0);
    close(fd);
    return FILE_REQUEST;

}

void http_conn::unmap(){
    if(my_file_address){
        munmap(my_file_address,m_file_stat.st_size);
        my_file_address=nullptr;
    }
}

bool http_conn::process_write(HTTP_CODE ret){
    switch(ret){
        default:break;
    }
    return false;
}


//由线程池中的工作线程调用，处理http请求的入口函数
void http_conn::process(){

    //解析http请求
    HTTP_CODE read_ret=process_read();
    if(read_ret==NO_REQUEST){
        //重新检测
        modfd(m_epollfd,m_sockfd,EPOLLIN);
        return;
    }

    //生成响应
    bool write_ret=process_write(read_ret);
    if(!write_ret){
        close_conn();
    }

    //因为使用了oneshot 所以每次使用完要重新添加？
    modfd(m_epollfd,m_sockfd,EPOLLOUT);

}